import { _decorator, Component, Node, SpriteAtlas } from 'cc';
import { addP2D, compareZIndex, GridForm, GridFormOrthogonal, KKP2D, KKP2DTable, KKP2DTree } from '@coldcloude/kakera';
import { K2D, K2DU, KCanceler, KList, KMap, KNumTable, KScheduler, KStrTable, KWeightSampler, Random } from '@coldcloude/kai2';
import { AreaTable, C_SHAPE, C_TOWER, combineTower, createNode, G_INVALID, G_USED, G_VALID, GCTDBoard, GCTDCandidateConfig, GCTDTower, K$ } from './Common';
import { BattleBoardController } from './BattleBoardController';
import { NodeEx } from './lib/kakera-cocos/scripts/NodeEx';
import { GCTD_CFG, GCTD_CFG_UI, GCTD_E_CFG_UI, GCTD_SG_CFG, GCTD_SG_PH_BT_CFG, POSGEN_RANDOM, stageName } from './Constant';
import { BattleTowerController } from './BattleTowerController';
import { BattleBlockController } from './BattleBlockController';
import { KKAnimeHub } from './lib/kakera-cocos/scripts/Animator';
import { BattleMobController } from './BattleMobController';
import { BattleBulletController } from './BattleBulletController';
const { ccclass, property } = _decorator;

const ST_INIT = 0;
const ST_PREPARE = 1;
const ST_BATTLE = 2;
const ST_DEFEAT = 3;
const ST_VICTORY = 4;

const BR_UNKNOWN = 0;
const BR_DEFEAT = 1;
const BR_VICTORY = 2;

const AREA_STEP = 8;

type BattleTowerBuff = {
    attackAdjust:number;
    intervalAdjust:number;
    duration:number;
    time:number;
}

type BattleMobBuff = {
    attackAdjust:number;
    movementAdjust:number;
    duration:number;
    time:number;
}

type BattleTowerState = {
    attack:number;
    defense:number;
    interval:number;
    currDefense:number;
    currShield:number;
    buffs:KList<BattleTowerBuff>;
    dying:boolean;
    attackStopping:boolean;
    attackCancler:KCanceler;
}

type CtorType<C> = new (...args:any[])=>C;

class EntityWithUI<C extends Component,E extends GCTD_E_CFG_UI> {
    cctor:CtorType<C>;
    controller:C;
    uiCfg:E;
    constructor(cctor:CtorType<C>){
        this.cctor = cctor;
    }
    nodeInit(){
        const node = createNode();
        this.controller = node.addComponent(this.cctor);
    }
    nodeRemove(){
        this.controller.node.removeFromParent();
    }
    nodeAdd(to:Node){
        to.addChild(this.controller.node);
    }
    getUIPos(){
        const nodePos = this.controller.node.position;
        const center = this.uiCfg.center
        return addP2D({x:nodePos.x,y:nodePos.y},center);
    }
    moveTo(pos:KKP2D){
        this.controller.node.setPosition(pos.x,pos.y);
    }
    move(vec:KKP2D){
        const position = this.controller.node.getPosition();
        this.moveTo(addP2D(position,vec));
    }
    rotateTo(a:number){
        this.controller.node.angle = a;
    }
}

class Tower extends EntityWithUI<BattleTowerController,GCTD_E_CFG_UI> {
    tower:GCTDTower;
    position:KKP2D;
    candidateNode:K$<Tower>;
    constructor(){
        super(BattleTowerController);
    }
    getUIPos(){
        const p = super.getUIPos();
        return addP2D(p,GCTD_CFG_UI.positions.board);
    }
    //state
    state:BattleTowerState;
}

class Block extends EntityWithUI<BattleBlockController,GCTD_E_CFG_UI> {
    shape:KKP2D[];
    candidateNode:K$<Block>;
    constructor(){
        super(BattleBlockController);
    }
}

class Mob extends EntityWithUI<BattleMobController,GCTD_E_CFG_UI> {
    name:string;
    constructor(){
        super(BattleMobController);
    }
    //state
    attack:number;
    defense:number;
    interval:number;
    movement:number;
    energy:number;
    dyingLimit:number;
    direction:KKP2D;
    buffs = new KList<BattleMobBuff>();
    dying = false;
    //tmp
    currMove:KKP2D;
    currMovingPolygon:K2D.Vector2D[]
    attackTowerPos:KKP2D;
    attackTowerCanceler:KCanceler;
}

class Bullet extends EntityWithUI<BattleBulletController,GCTD_E_CFG_UI> {
    name: string;
    constructor(){
        super(BattleBulletController);
    }
    //state
    attack: number;
    velocity: number;
    rotate: number;
    hittingLimit: number;
    hitting = false;
    //tmp
    currMove:KKP2D;
    currMovingPolygon:K2D.Vector2D[];
}

type StageBatchProgress = {
    cfg: GCTD_SG_PH_BT_CFG,
    cnt: number,
    xGen: (i:number)=>number,
    yGen: (i:number)=>number
}

const createPositionGenerator = (total:number,init:number,range:number,name:string,random?:Random)=>{
    if(name===POSGEN_RANDOM){
        random = random||new Random();
        return ()=>{
            return init+random.nextFloat()*range;
        }
    }
    else{
        const ease = K2DU.EasingFunction[name];
        return (i:number)=>{
            return init+ease((i+0.5)/total)*range;
        }
    }
}

class Battle {
    //config
    stage:GCTD_SG_CFG;
    //ui
    director:BattleDirector;
    towerMap = new KKP2DTable<Tower>();
    mobList = new KList<Mob>();
    bulletList = new KList<Bullet>();
    //stage progress
    running = true;
    time = 0;
    phi = -1;
    nextTime = 0;
    batchTime = 0;
    progresses = new KList<StageBatchProgress>();
    result = BR_UNKNOWN;
    //scheduler
    scheduler = new KScheduler();
    //random
    random = new Random();
    //context
    currentMobMap:KKP2DTable<K$<Mob>>;
    constructor(director:BattleDirector,towerMap:KMap<KKP2D,Tower>,stage:GCTD_SG_CFG){
        this.director = director;
        towerMap.foreach((pos,tower)=>{
            this.createTowerState(pos,tower);
            this.towerMap.set(pos,tower);
        });
        this.stage = stage;
    }
    towerScheduleAttack(tower:Tower){
        tower.state.attackCancler = this.scheduler.setInterval(()=>{
            if(tower.state.dying||tower.state.attackStopping){
                tower.state.attackStopping = false;
                this.towerCancelAttack(tower);
            }
            else if(this.currentMobMap){
                //search for mob
                const pos = tower.getUIPos();
                let tD:number;
                let tMP:KKP2D;
                this.currentMobMap.foreach((mp)=>{
                    const d = K2DU.distance(pos.x,pos.y,mp.x,mp.y);
                    if(tD===undefined||d<tD){
                        tD = d;
                        tMP = mp;
                    }
                });
                //build bullets
                if(tMP){
                    const t = tower.tower;
                    const config = GCTD_CFG.towers[t.type];
                    const name = config.bulletName[t.level];
                    const velocity = config.bulletVelocities[t.level];
                    const rotates = config.bulletRotateLists[t.level];
                    const bis = config.bulletIntervals[t.level];
                    const attack = tower.state.attack;
                    const angle = K2DU.angle(pos.x,pos.y,tMP.x,tMP.y);
                    const create = (i:number)=>{
                        const bullet = this.createBullets(name,attack,velocity,angle+rotates[i],pos);
                        this.bulletList.push(bullet);
                    };
                    if(bis===0){
                        //multiple bullets at same time
                        for(let i=0; i<rotates.length; i++){
                            create(i);
                        }
                    }
                    else{
                        //bullets in sequence
                        let i = 0;
                        const canceler = this.scheduler.setInterval(()=>{
                            if(i>=rotates.length){
                                canceler.cancel();
                            }
                            else{
                                create(i);
                                i++;
                            }
                        },0,bis);
                    }
                }
            }
        },0,tower.state.interval);
    }
    towerCancelAttack(tower:Tower){
        if(tower.state.attackCancler){
            tower.state.attackCancler.cancel();
            tower.state.attackCancler = undefined;
        }
    }
    mobScheduleAttack(mob:Mob,tTower:Tower){
        mob.attackTowerPos = tTower.position;
        mob.attackTowerCanceler = this.scheduler.setInterval(()=>{
            tTower.state.currDefense -= mob.attack;
            if(tTower.state.currDefense<=0){
                //tower died, remove target
                this.killTower(tTower);
                this.mobCancelAttack(mob);
            }
        },0,mob.interval)
    }
    mobCancelAttack(mob:Mob){
        mob.attackTowerCanceler.cancel();
        mob.attackTowerCanceler = undefined;
        mob.attackTowerPos = undefined;
    }
    createTowerState(pos:KKP2D,tower:Tower){
        const t = tower.tower;
        const config = GCTD_CFG.towers[t.type];
        tower.state = {
            attack: config.attacks[t.level],
            defense: config.defenses[t.level],
            interval: config.intervals[t.level],
            currDefense: 0,
            currShield: 0,
            buffs: new KList<BattleTowerBuff>(),
            dying: false,
            attackStopping: false,
            attackCancler: undefined
        };
        tower.state.currDefense = tower.state.defense;
    }
    killTower(tower:Tower){
        tower.state.dying = true;
    }
    createMob(name:string,pos:KKP2D){
        const mob = new Mob();
        //config
        mob.name = name;
        const config = GCTD_CFG.mobs[mob.name];
        mob.attack = config.attack;
        mob.defense = config.defense;
        mob.interval = config.interval;
        mob.movement = config.movement;
        mob.energy = config.energy;
        mob.dyingLimit = config.dyingLimit;
        //ui
        mob.uiCfg = GCTD_CFG_UI.mobs[mob.name];
        mob.nodeInit();
        this.director._initMobController(mob);
        mob.nodeAdd(this.director.mobPanelNode);
        mob.moveTo(pos);
        return mob;
    }
    killMob(mob$:K$<Mob>){
        const mob = mob$.value;
        mob.dying = true;
        this.scheduler.setTimeout(()=>{
            this.mobList.removeNode(mob$);
        },mob.dyingLimit);
    }
    createBullets(name:string,attack:number,velocity:number,rotate:number,pos:KKP2D){
        const bullet = new Bullet();
        bullet.name = name;
        bullet.attack = attack;
        bullet.velocity = velocity;
        bullet.rotate = rotate;
        //ui
        bullet.uiCfg = GCTD_CFG_UI.bullets[bullet.name];
        bullet.nodeInit();
        this.director._initBulletController(bullet);
        bullet.nodeAdd(this.director.mobPanelNode);
        bullet.moveTo(pos);
        bullet.rotateTo(rotate);
        return bullet;
    }
    killBullet(bullet$:K$<Bullet>){
        const bullet = bullet$.value;
        bullet.hitting = true;
        this.scheduler.setTimeout(()=>{
            this.bulletList.removeNode(bullet$);
        },bullet.hittingLimit);
    }
    update(dt:number){
        if(this.running){
            this.time += dt;
            //move to next stage
            if(this.phi<this.stage.phases.length&&(this.time>this.nextTime||this.mobList.size===0)){
                this.phi++;
                if(this.phi>=0&&this.phi<this.stage.phases.length){
                    const phase = this.stage.phases[this.phi];
                    this.nextTime = this.time+phase.maxDuration;
                    this.batchTime = this.time;
                    for(const cfg of phase.batches){
                        const xGen = createPositionGenerator(cfg.total,cfg.initRectPosition.x,cfg.initRectSize.width,cfg.initXGenerator,this.random);
                        const yGen = createPositionGenerator(cfg.total,cfg.initRectPosition.y,cfg.initRectSize.height,cfg.initYGenerator,this.random);
                        this.progresses.push({
                            cfg:cfg,
                            cnt:0,
                            xGen:xGen,
                            yGen:yGen
                        });
                    }
                }
            }
            //add mobs
            this.progresses.foreach(progress=>{
                const batch = progress.cfg;
                const require = Math.min(batch.total,Math.floor((this.time-this.batchTime-batch.start)/batch.interval));
                while(progress.cnt<require){
                    const mob = this.createMob(batch.mob,{
                        x: progress.xGen(progress.cnt),
                        y: progress.yGen(progress.cnt)
                    });
                    this.mobList.push(mob);
                    progress.cnt++;
                }
            });
            this.progresses.removeIf(progress=>{
                const cfg = progress.cfg;
                return this.time>this.batchTime+cfg.start+cfg.total*cfg.interval;
            });
            //find all alive tower
            const aliveTowerMap = new KKP2DTree<Tower>();
            this.towerMap.foreach((pos,tower)=>{
                if(!tower.state.dying){
                    aliveTowerMap.set(tower.getUIPos(),tower);
                }
            });
            //test if battle end
            if(aliveTowerMap.size===0){
                //defeat
                this.result = BR_DEFEAT;
                this.running = false;
            }
            else if(this.phi>=this.stage.phases.length&&this.progresses.size===0&&this.mobList.size===0){
                //victory
                this.result = BR_VICTORY;
                this.running = true;
            }
            //find all mob, and calculate last move
            const mobMap = new KKP2DTable<K$<Mob>>();
            let mob$ = this.mobList.head;
            while(mob$){
                const mob = mob$.value;
                const pos = mob.getUIPos();
                mobMap.set(pos,mob$);
                //check attacking
                let tTower:Tower = undefined;
                if(mob.attackTowerPos){
                    tTower = aliveTowerMap.get(mob.attackTowerPos);
                }
                //move, if not collision with tower
                if(!tTower){
                    const ui = mob.uiCfg;
                    const x1 = pos.x-ui.center.x;
                    const y1 = pos.y-ui.center.y;
                    const x2 = x1+ui.collision.width;
                    const y2 = y1+ui.collision.height;
                    const dx = mob.movement*mob.direction.x;
                    const dy = mob.movement*mob.direction.y;
                    let minRatio = 1;
                    aliveTowerMap.foreach((tp,t)=>{
                        //collision with alive tower
                        if(!t.state.dying){
                            const tui = t.uiCfg;
                            const tx1 = tp.x-tui.center.x;
                            const ty1 = tp.y-tui.center.y;
                            const tx2 = tx1+tui.collision.width;
                            const ty2 = ty1+tui.collision.height;
                            const r = K2DU.calculateMovingOverlapRatio2(x1,y1,x2,y2,dx,dy,tx1,ty1,tx2,ty2);
                            if(r&&r[0]<minRatio){
                                minRatio = r[0];
                                tTower = t;
                            }
                        }
                    });
                    mob.currMove = minRatio===0?{x:0,y:0}:{x:dx*minRatio,y:dy*minRatio};
                    mob.currMovingPolygon = K2DU.buildMovingPolygon(x1,y1,x2,y2,mob.currMove.x,mob.currMove.y);
                }
                //collision with power, attack
                if(tTower){
                    //attack this time
                    if(!mob.attackTowerPos){
                        //empty target, start
                        this.mobScheduleAttack(mob,tTower);
                    }
                    else if(compareZIndex(tTower.position,mob.attackTowerPos)!==0){
                        //another target
                        this.mobCancelAttack(mob);
                        this.mobScheduleAttack(mob,tTower);
                    }
                }
                else{
                    if(mob.attackTowerPos){
                        //cancel current target
                        this.mobCancelAttack(mob);
                    }
                }
                mob$ = mob$.next;
            }
            //tower attack
            if(mobMap.size>0){
                aliveTowerMap.foreach((pos,tower)=>{
                    tower.state.attackStopping = false;
                    if(!tower.state.attackCancler){
                        this.towerScheduleAttack(tower);
                    }
                });
            }
            else{
                aliveTowerMap.foreach((pos,tower)=>{
                    tower.state.attackStopping = true;
                });
            }
            //find all bullet
            const bulletCurrMap = new KKP2DTable<K$<Bullet>>();
            let bullet$ = this.bulletList.head;
            while(bullet$){
                const bullet = bullet$.value;
                const pos = bullet.getUIPos();
                bulletCurrMap.set(pos,bullet$);
                //move
                const rm = K2D.rotate(bullet.rotate);
                const vv = [bullet.velocity,0,0];
                const movVec = K2D.transform(rm,vv);
                bullet.currMove = {x:movVec[0],y:movVec[1]};
                //move polygon
                const ui = bullet.uiCfg;
                const x1 = -ui.center.x;
                const y1 = -ui.center.y;
                const x2 = x1+ui.collision.width;
                const y2 = y1+ui.collision.height;
                const poly = K2DU.buildMovingPolygon(x1,y1,x2,y2,vv[0],vv[1]);
                for(let i=0; i<poly.length; i++){
                    poly[i] = K2D.transform(rm,poly[i]);
                    poly[i] = K2D.add(poly[i],[pos.x,pos.y]);
                }
                bullet.currMovingPolygon = poly;
                bullet$ = bullet$.next;
            }
            //bullet mob collision
            const mobAreaMap = new AreaTable<K$<Mob>>(AREA_STEP);
            mobMap.foreach((pos,mob$)=>{
                mobAreaMap.put(pos,mob$);
            });
            bulletCurrMap.foreach((pos,bullet$)=>{
                const bullet = bullet$.value;
                if(!bullet.hitting){
                    const bulletPoly = bullet.currMovingPolygon;
                    const areaMaps = mobAreaMap.getAll(pos);
                    let cMP:KKP2D;
                    let cMob$:K$<Mob>;
                    areaMaps.foreach((p,amap)=>{
                        amap.foreach((mp,mob$)=>{
                            const mobPoly = mob$.value.currMovingPolygon;
                            if(K2DU.overlapConvexConvex(bulletPoly,mobPoly)){
                                cMP = mp;
                                cMob$ = mob$;
                                return true;
                            }
                            if(cMob$&&cMP){
                                return true;
                            }
                        });
                    });
                    if(cMob$&&cMP){
                        //bullet die
                        this.killBullet(bullet$);
                        //hit mob
                        const cMob = cMob$.value;
                        cMob.defense -= bullet.attack;
                        if(cMob.defense<=0){
                            this.killMob(cMob$);
                        }
                    }
                }
            });
            //change mob state
            mobMap.foreach((pos,mob$)=>{
                const mob = mob$.value;
                if(!mob.dying){
                    //still alive
                    mob.move(mob.currMove);
                    mob.currMove = undefined;
                    mob.currMovingPolygon = undefined;
                    //toward nearest tower
                    let nd = -1;
                    let np:KKP2D;
                    aliveTowerMap.foreach((pt,tt)=>{
                        const d = K2DU.distance(pt.x,pt.y,pos.x,pos.y);
                        if(nd<0||d<nd){
                            nd = d;
                            np = pt;
                        }
                    });
                    const vt = K2D.uniform([np.x-pos.x,np.y-pos.y]);
                    mob$.value.direction = {x:vt[0],y:vt[1]};
                }
            });
            //change bullet state
            bulletCurrMap.foreach((pos,bullet$)=>{
                const bullet = bullet$.value;
                if(!bullet.hitting){
                    //still alive
                    bullet.move(bullet.currMove);
                    bullet.currMove = undefined;
                    bullet.currMovingPolygon = undefined;
                }
            });
            //scheduled tasks
            this.currentMobMap = mobMap;
            this.scheduler.tick(dt);
        }
    }
}

@ccclass('BattleDirector')
export class BattleDirector extends Component {

    @property(Node)
    uiNode:Node;

    @property(Node)
    worldNode:Node;

    @property(SpriteAtlas)
    atlas:SpriteAtlas;

    towerAnimeHub = new KStrTable<KNumTable<KKAnimeHub>>();

    mobAnimeHub = new KStrTable<KKAnimeHub>();

    bulletAnimeHub = new KStrTable<KKAnimeHub>();

    board:GCTDBoard;
    towerMap = new KKP2DTable<Tower>();
    candidateTowers = new KList<Tower>();
    candidateBlocks = new KList<Block>();

    candidateRandom = new Random();
    levelRandom = new Random();

    state = ST_INIT;

    stageLv:number;
    stageSt:number;

    gridForm:GridForm;

    boardNode:NodeEx;
    boardContoller:BattleBoardController;

    towerPanelNode:NodeEx;

    candidatePanelNode:NodeEx;

    mobPanelNode:NodeEx

    _getTowerAnimeHub(type:string,level:number){
        return this.towerAnimeHub.computeIfAbsent(type,()=>new KNumTable<KKAnimeHub>()).computeIfAbsent(level,()=>{
            const aniColle = GCTD_CFG_UI.towers[type][level].animes;
            return new KKAnimeHub(aniColle);
        });
    }

    _getMobAnimeHub(name:string){
        return this.mobAnimeHub.computeIfAbsent(name,()=>{
            const aniColle = GCTD_CFG_UI.mobs[name].animes;
            return new KKAnimeHub(aniColle);
        });
    }

    _getBulletAnimeHub(name:string){
        return this.bulletAnimeHub.computeIfAbsent(name,()=>{
            const aniColle = GCTD_CFG_UI.mobs[name].animes;
            return new KKAnimeHub(aniColle);
        });
    }

    _initTowerCotroller(t:Tower){
        t.controller.hub = this._getTowerAnimeHub(t.tower.type,t.tower.level);
        t.controller.atlas = this.atlas;
        t.controller.init();
    }

    _updateTowerController(t:Tower){
        const hub = this._getTowerAnimeHub(t.tower.type,t.tower.level);
        t.controller.hub = hub;
        t.controller.animator.setHub(hub);
    }

    _initBlockController(b:Block){
        b.controller.shape = b.shape;
        b.controller.gridForm = this.gridForm;
        b.controller.atlas = this.atlas;
        b.controller.init();
    }

    _initMobController(m:Mob){
        const hub = this._getMobAnimeHub(m.name);
        m.controller.hub = hub;
        m.controller.animator.setHub(hub);
        m.controller.atlas = this.atlas;
        m.controller.init();
    }

    _initBulletController(b:Bullet){
        const hub = this._getBulletAnimeHub(b.name);
        b.controller.hub = hub;
        b.controller.animator.setHub(hub);
        b.controller.atlas = this.atlas;
        b.controller.init();
    }

    combineTowerCandidateTo(src:Tower,target:Tower):boolean{
        const r = combineTower(src.tower,target.tower);
        if(r){
            this.candidateTowers.removeNode(src.candidateNode);
            src.nodeRemove();
            this._updateTowerController(target);
        }
        return r;
    }

    combineTowerBattleTo(src:Tower,target:Tower):boolean{
        const r = combineTower(src.tower,target.tower);
        if(r){
            this.towerMap.get(src.position,true);
            src.nodeRemove();
            this._updateTowerController(target);
        }
        return r;
    }

    moveTowerToBattle(src:Tower,targetPos:KKP2D):boolean{
        let r = false;
        const shape = GCTD_CFG.towers[src.tower.type].shape;
        if(this.board.testBlock(targetPos,shape,G_VALID)){
            this.board.setBlock(targetPos,shape,G_USED);
            //remove
            this.candidateTowers.removeNode(src.candidateNode);
            src.candidateNode = undefined;
            src.nodeRemove();
            //add
            this.towerMap.set(targetPos,src);
            src.position = targetPos;
            src.nodeAdd(this.towerPanelNode);
            r = true;
        }
        return r;
    }

    moveTowerToCandidate(src:Tower){
        const shape = GCTD_CFG.towers[src.tower.type].shape;
        this.board.setBlock(src.position,shape,G_VALID);
        //remove
        this.towerMap.get(src.position);
        src.position = undefined;
        src.nodeRemove();
        //add
        src.candidateNode = this.candidateTowers.push(src);
        src.nodeAdd(this.candidatePanelNode);
    }

    moveBlockToBattle(src:Block,targetPos:KKP2D):boolean{
        let r = false;
        if(this.board.testBlock(targetPos,src.shape,G_INVALID)){
            this.board.setBlock(targetPos,src.shape,G_VALID);
            this.candidateBlocks.removeNode(src.candidateNode);
            src.candidateNode = undefined;
            src.nodeRemove();
            r = true;
        }
        return r;
    }

    moveBlockToCandidate(srcPos:KKP2D,shape:KKP2D[]):Block{
        let r:Block;
        if(this.board.testBlock(srcPos,shape,G_VALID)){
            this.board.setBlock(srcPos,shape,G_INVALID);
            r = new Block();
            r.shape = shape;
            r.candidateNode = this.candidateBlocks.push(r);
            r.nodeInit();
            r.nodeAdd(this.candidatePanelNode);
        }
        return r;
    }

    discardCandidates(){
        this.candidateTowers.foreach(tower=>{
            tower.nodeRemove();
        });
        this.candidateTowers.clear();
        this.candidateBlocks.foreach(block=>{
            block.nodeRemove();
        });
        this.candidateBlocks.clear();
    }

    buildCandidates(configs:[bigint,GCTDCandidateConfig][],levels:[bigint,number][],total:number):[Tower[],Block[]]{
        const towers = [];
        const blocks = [];
        const wsampler = new KWeightSampler(configs,{random:this.candidateRandom});
        const lwsampler = new KWeightSampler(levels,{random:this.levelRandom});
        for(let i=0; i<total; i++){
            const config = wsampler.sample();
            if(config.category===C_TOWER){
                const t = {
                    type: config.type,
                    level: lwsampler.sample()
                } as GCTDTower;
                const tower = new Tower();
                tower.tower = t;
                tower.candidateNode = this.candidateTowers.push(tower);
                //ui
                tower.uiCfg = GCTD_CFG_UI.towers[t.type][t.level];
                tower.nodeInit();
                this._initTowerCotroller(tower);
                tower.nodeAdd(this.candidatePanelNode);
                towers.push(tower);
            }
            else if(config.category===C_SHAPE){
                const shape = GCTD_CFG.shapes[config.type];
                const block = new Block();
                block.shape = shape;
                block.candidateNode = this.candidateBlocks.push(block);
                block.nodeInit();
                this._initBlockController(block);
                block.nodeAdd(this.candidatePanelNode);
                blocks.push(block);
            }
        }
        return [towers,blocks];
    }

    setStage(lv:number,st:number){
        this.stageLv = lv;
        this.stageSt = st;
    }

    battle:Battle;

    energy = 0;

    battleStart(){
        if(this.state===ST_PREPARE){
            this.state = ST_BATTLE;
            const stage = GCTD_CFG.stages[stageName(this.stageLv,this.stageSt)];
            if(stage){
                this.battle = new Battle(this,this.towerMap,stage);
            }
        }
    }

    start(){
        //board
        this.boardNode = createNode();
        this.worldNode.addChild(this.boardNode);
        this.gridForm = new GridFormOrthogonal(GCTD_CFG_UI.block.tileWidth,GCTD_CFG_UI.block.tileHeight);
        this.boardContoller = this.boardNode.addComponent(BattleBoardController);
        this.boardContoller.board = this.board;
        this.boardContoller.gridForm = this.gridForm;
        this.boardContoller.atlas = this.atlas;
        this.boardContoller.init();
        //tower panel
        this.towerPanelNode = createNode();
        this.worldNode.addChild(this.towerPanelNode);
        //candidate panel
        this.candidatePanelNode = createNode();
        this.worldNode.addChild(this.candidatePanelNode);
        //mob panel
        this.mobPanelNode = createNode();
        this.worldNode.addChild(this.mobPanelNode);
        //start
        this.state = ST_PREPARE;
    }

    update(deltaTime:number){
        this.boardContoller.render(deltaTime);
        this.towerMap.foreach((pos,tower)=>{
            tower.controller.render(deltaTime);
        });
        this.candidateTowers.foreach(tower=>{
            tower.controller.render(deltaTime);
        });
        this.candidateBlocks.foreach(block=>{
            block.controller.render(deltaTime);
        });
        if(this.state===ST_BATTLE){
            if(this.battle){
                this.battle.update(deltaTime);
                if(this.battle.result===BR_DEFEAT){
                    //defeat
                    this.battle = undefined;
                    this.state = ST_DEFEAT;
                }
                else if(this.battle.result===BR_VICTORY){
                    //go to next stage
                    this.battle = undefined;
                    this.stageSt++;
                    this.state = ST_PREPARE;
                }
            }
            else{
                //no battle found, end as victory
                this.state = ST_VICTORY;
            }
        }
    }
}


